package com.cmy.init;

import com.cmy.bean.DdlHistory;
import com.cmy.bean.DdlRecord;
import com.cmy.constant.DDLConstant;
import com.cmy.dbTransfer.DBHandler;
import com.cmy.factory.DataBaseTransferFactory;
import com.cmy.service.DdlHistoryService;
import com.cmy.service.RecordHandler;
import com.cmy.utils.ThrowableUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.CRC32;

/**
 * @author : Cmy
 * @date : 2024-09-14 16:18
 */
@Slf4j
@Component
public class InitTableRecord {

    @Autowired
    private Environment env;
    @Autowired
    private DdlHistoryService ddlHistoryService;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private RecordHandler recordHandler;

    @PostConstruct
    public void initRecords(){
        log.info("333333333333333333 - DataBase Tables Records Initialize End - 333333333333333333");
        String property = env.getProperty("spring.auto-ddl.enabled");
        String username = env.getProperty("spring.datasource.username");
        boolean ddlEnabled = property==null || Boolean.parseBoolean(property);

        if (ddlEnabled){
            try {
                DBHandler dbHandler = DataBaseTransferFactory.getDBTransfer(DDLConstant.DB_TYPE);
                JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
                //这里执行sql
                Map<String, DdlHistory> ddlHistoryMap = new HashMap<>();
                //按照id升序
                List<DdlHistory> list = ddlHistoryService.cacheList(jdbcTemplate);
                if (!list.isEmpty()){
                    for (DdlHistory ddlHistory : list) {
                        String version = ddlHistory.getVersion();
                        if ("R".equals(version)) {
                            //key: description 同description 后面的会覆盖前面的 ABA问题,防止第二个A不执行
                            ddlHistoryMap.put(ddlHistory.getDescription(),ddlHistory);
                        }
                    }
                }

                List<Resource> filteredResourceList = DDLConstant.nameResourceMap
                        .entrySet()
                        .stream()
                        .filter(entry->DDLConstant.R_VERSION_PATTERN_CSV.matcher(entry.getKey()).find())
                        .map(Map.Entry::getValue)
                        .collect(Collectors.toList());

                for (Resource resource : filteredResourceList) {
                    log.info("======");
                    String filename = resource.getFilename();
                    if (StringUtils.isBlank(filename)){
                        continue;
                    }
                    String[] arr = filename.split("__");
                    if (arr.length<2){
                        continue;
                    }
                    boolean inSideJar = Objects.equals(resource.getURL().getProtocol(), "jar");

                    String version = arr[0];
                    String description = arr[1].substring(0,arr[1].lastIndexOf("."));
                    InputStream inputStream = resource.getInputStream();
                    Long checksum = getFileChecksum(inputStream);

                    if (ddlHistoryMap.containsKey(description)){
                        //检查文件是否变动过

                        DdlHistory ddlHistory = ddlHistoryMap.get(description);
                        Long exCheckSum = ddlHistory.getChecksum();
                        if (Objects.equals(checksum, exCheckSum)) {
                            log.info("csv [{}]:[{}] has already cover",inSideJar?"inside":"outside",filename);
                            log.info("======");
                            continue;
                        }
                    }

                    DdlHistory ddlHistory = new DdlHistory();
                    ddlHistory.setVersion(version);
                    ddlHistory.setDescription(description);
                    ddlHistory.setScript(filename);
                    ddlHistory.setChecksum(checksum);
                    ddlHistory.setInstalledBy(username);
                    ddlHistory.setSuccess(true);
                    ddlHistory.setInstalledOn(LocalDateTime.now());

                    boolean truncateFlag = true;
                    long start = System.currentTimeMillis();
                    try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))) {
                        CSVParser csvParser = CSVFormat.DEFAULT.parse(reader);
                        Iterator<CSVRecord> iterator = csvParser.iterator();

                        String tableName = iterator.next().get(0).trim();
                        truncateFlag  = Boolean.parseBoolean(iterator.next().get(0).trim());
                        if (truncateFlag){
                            String tableTruncateSql = dbHandler.generateTableTruncateTable(tableName);
                            jdbcTemplate.execute(tableTruncateSql);
                            log.warn("enable truncate flag,will truncate table : {}", tableName);
                        }

                        //构建 ddl 记录对象
                        DdlRecord ddlRecord = new DdlRecord();
                        // 3. 获取字段列表
                        CSVRecord headers = iterator.next();
                        ddlRecord.setTableName(tableName);
                        List<String> columns = new ArrayList<>();
                        for (String header : headers) {
                            if (!header.trim().isEmpty()) {
                                columns.add(header.trim());
                            }
                        }
                        ddlRecord.setFiledNames(columns);

                        // 4. 处理数据行
                        List<Map<String,Object>> rowList = new ArrayList<>();
                        while (iterator.hasNext()) {
                            CSVRecord record = iterator.next();
                            Map<String, Object> rowMap = new HashMap<>();
                            for (int i = 0; i < columns.size(); i++) {
                                String filedName = columns.get(i);
                                String smallCamelFiledName = underlineToSmallCamel(filedName);
                                String value = record.get(i).trim();
                                rowMap.put(smallCamelFiledName, value);
                            }
                            rowList.add(rowMap);
                        }
                        ddlRecord.setRecordList(rowList);
                        if (!rowList.isEmpty()){
                            if (truncateFlag){
                                //默认处理
                                dbHandler.batchInsert(ddlRecord,jdbcTemplate);
                            }else {
                                //分发给下游 以 个性化处理
                                recordHandler.distribute(ddlRecord);
                            }
                        }
                    }catch (Exception e){
                        log.error(ThrowableUtil.getStackTrace(e));
                        ddlHistory.setSuccess(false);
                    }
                    long end = System.currentTimeMillis();
                    ddlHistory.setExecutionTime(end - start);
                    ddlHistoryService.save(ddlHistory, jdbcTemplate);
                    if (truncateFlag) {
                        log.info("csv [{}]:[{}] cover successfully",inSideJar?"inside":"outside",filename);
                    }else {
                        log.info("csv [{}]:[{}] distribute successfully",inSideJar?"inside":"outside",filename);
                    }
                    log.info("======");
                }
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }
        log.info("333333333333333333 - DataBase Tables Records Initialize End - 333333333333333333");
        log.info("--------------------------------------------------------------------------------");
    }

    private Long getFileChecksum(File file){
        final CRC32 crc32 = new CRC32();
        try (FileInputStream fis = new FileInputStream(file)) {
            byte[] buffer = new byte[4096];
            while (fis.read(buffer) > 0){
                crc32.update(buffer);
            }
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            return -1L;
        }
        return crc32.getValue();
    }

    private Long getFileChecksum(InputStream fis){
        final CRC32 crc32 = new CRC32();
        try {
            byte[] buffer = new byte[4096];
            while (fis.read(buffer) > 0){
                crc32.update(buffer);
            }
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            return -1L;
        }
        return crc32.getValue();
    }

    /**
     * 下划线转小驼峰 (首字母小写)
     * 示例： user_name → userName
     */
    public static String underlineToSmallCamel(String underlineName) {
        if (StringUtils.isBlank(underlineName)) {
            return underlineName;
        }

        StringBuilder sb = new StringBuilder();
        boolean nextUpperCase = false;

        for (int i = 0; i < underlineName.length(); i++) {
            char c = underlineName.charAt(i);

            if (c == '_') {
                nextUpperCase = true;
            } else {
                if (nextUpperCase) {
                    sb.append(Character.toUpperCase(c));
                    nextUpperCase = false;
                } else {
                    sb.append(Character.toLowerCase(c));
                }
            }
        }
        return sb.toString();
    }


}

    